#include <stdio.h>				// printf, etc
#include <stdint.h>				// standard integer types, e.g., uint32_t
#include <signal.h>				// for signal handler
#include <stdlib.h>				// exit() and EXIT_FAILURE
#include <string.h>				// strerror() function converts errno to a text string for printing
#include <fcntl.h>				// for open()
#include <errno.h>				// errno support
#include <assert.h>				// assert() function
#include <unistd.h>				// sysconf() function, sleep() function
#include <sys/mman.h>			// support for mmap() function
#include <math.h>				// for pow() function used in RAPL computations
#include <time.h>
#include <stdbool.h>
#include <sys/time.h>			// for gettimeofday
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sample_cha.h"

#define CHA_MSR_PMON_BASE 0x0E00L
#define CHA_MSR_PMON_CTL_BASE 0x0E01L
#define CHA_MSR_PMON_FILTER0_BASE 0x0E05L
#define CHA_MSR_PMON_FILTER1_BASE 0x0E06L
#define CHA_MSR_PMON_STATUS_BASE 0x0E07L
#define CHA_MSR_PMON_CTR_BASE 0x0E08L

#define SAMPLE_INTERVAL_SECS 1

#define NUM_CHA_BOXES 24



uint64_t cha_delta[NUM_CHA_BOXES] = {0};
uint64_t prev_cha_counts[NUM_CHA_BOXES] = {0};
uint64_t cur_cha_counts[NUM_CHA_BOXES] = {0};

int msr_fd;

static uint64_t cha_event = 0x00400000;
static uint64_t cha_filter1 = 0x0000003B;


static inline __attribute__((always_inline)) unsigned long rdtsc()
{
   unsigned long a, d;

   __asm__ volatile("rdtsc" : "=a" (a), "=d" (d));

   return (a | (d << 32));
}


static inline __attribute__((always_inline)) unsigned long rdtscp()
{
   unsigned long a, d, c;

   __asm__ volatile("rdtscp" : "=a" (a), "=d" (d), "=c" (c));

   return (a | (d << 32));
}

extern inline __attribute__((always_inline)) int get_core_number()
{
   unsigned long a, d, c;

   __asm__ volatile("rdtscp" : "=a" (a), "=d" (d), "=c" (c));

   return ( c & 0xFFFUL );
}

    
// msr_fd must be open before executing this function
void sample_cha_counters(void) {
    int cha;
    uint64_t msr_num, msr_val;
    ssize_t ret;
    for (cha=0; cha<NUM_CHA_BOXES; cha++) {
            msr_num = CHA_MSR_PMON_CTR_BASE + (0x10 * cha);
            ret = pread(msr_fd, &msr_val, sizeof(msr_val), msr_num);
            if (ret != sizeof(msr_val)) {
                printf("ERROR: failed to read MSR %lx", msr_num);
                exit(-1);
            }
            prev_cha_counts[cha] = cur_cha_counts[cha];
            cur_cha_counts[cha] = msr_val;
            cha_delta[cha]= cur_cha_counts[cha] - prev_cha_counts[cha];
	}
}

void busy_loop(int secs){
    uint64_t start, last, elapsed, wait;
    wait = SAMPLE_INTERVAL_SECS*36*1e8;
    start = rdtscp();
    do{
        last = rdtscp();
        elapsed = last - start;
    }while(elapsed < wait);
}

SlicePair find_two_large_indices(){
    int found_count = 0;
    SlicePair slice;
    slice.idx1 = 0;
    slice.idx2 = 0;
    
    for(int i=0; i<NUM_CHA_BOXES; i++){
        if(cha_delta[i] > 1500){
            if(found_count == 0){
                slice.idx1 = i;
                found_count++;
            }else if(found_count == 1){
                slice.idx2 = i;
                found_count++;
            }
            
        }
    }
    if (found_count > 2) {
        printf("Warning: more than 2 CHA counters's value exceed the threshold!!!!\n");
    }
    if(found_count == 1){
        slice.idx2 = slice.idx1;
        printf("Two slices involved are the same: %2d %2d\n", slice.idx1, slice.idx2);
        return slice;
    }
    printf("Two slices involved: %2d %2d\n", slice.idx1, slice.idx2);
    
    return slice;
    
}

void program_cha_counters(){
    uint64_t ret;
    uint64_t msr_num, msr_val;
    int cha;
    cha_event = 0x401435;
    cha_filter1 = 0x40033;
    int cpu = get_core_number();

    // Open MSR file for this core
    char filename[100];
    sprintf(filename, "/dev/cpu/%d/msr", cpu);
    msr_fd = open(filename, O_RDWR);
    if(msr_fd == -1) {
        printf("An error occurred while opening msr file.\n");
    }
    for(cha = 0; cha < NUM_CHA_BOXES; cha++) {
        msr_num = CHA_MSR_PMON_CTL_BASE + (0x10 * cha) + 0; // counter 0
        msr_val = cha_event;
        ret = pwrite(msr_fd,&msr_val,sizeof(msr_val),msr_num);
        if (ret != 8) {
            printf("ERROR writing to MSR device, write %ld bytes\n", ret);
            exit(-1);
        }

        // Filters
        msr_num = CHA_MSR_PMON_FILTER1_BASE + (0x10 * cha); // Filter1
        msr_val = cha_filter1;
        ret = pwrite(msr_fd,&msr_val,sizeof(msr_val),msr_num);
        if (ret != 8) {
            printf("ERROR writing to MSR device, write %ld bytes\n", ret);
            exit(-1);
        }
    }
}





    


